Skip to content

Harden PubSub replay, Action, and DTLS security (OPC 10000-14 / RFC 9147)#4044

Merged
marcschier merged 5 commits into
OPCFoundation:masterfrom
marcschier:marcschier/review-fix/pubsub-dtls-security
Jul 21, 2026
Merged

Harden PubSub replay, Action, and DTLS security (OPC 10000-14 / RFC 9147)#4044
marcschier merged 5 commits into
OPCFoundation:masterfrom
marcschier:marcschier/review-fix/pubsub-dtls-security

Conversation

@marcschier

Copy link
Copy Markdown
Collaborator

Description

This draft is the focused PubSub/UDP/DTLS security split from #4021.

The changes:

  • enforce replay commit ordering: reject UADP messages that fail signature or required-security-mode checks before mutating replay state, and advance the DTLS replay window only after record authentication succeeds;
  • scope UADP sequence windows by (PublisherId, WriterGroupId, SecurityTokenId) while retaining global per-key nonce uniqueness across all publisher and writer-group scopes;
  • serialize outbound DTLS record sequence allocation and cryptographic use, reconstruct truncated DTLS sequence numbers, and reject epoch sequence exhaustion;
  • keep unsecured Action opt-in on each registered responder instead of promoting it to connection-wide policy;
  • pin connection-ID-less DTLS associations to the endpoint authenticated by the handshake and drop records from other peers before replay processing.

These changes implement the replay and nonce requirements in OPC 10000-14 §7.2.4.4.2 and the DTLS security considerations in RFC 9147 §11.

Validation

  • git diff --check origin/master...HEAD — passed.
  • dotnet test tests\Opc.Ua.PubSub.Tests\Opc.Ua.PubSub.Tests.csproj --configuration Release --framework net10.0 -p:CustomTestTarget=net10.0 — 1,324 passed, 1 skipped.
  • dotnet test tests\Opc.Ua.PubSub.Udp.Tests\Opc.Ua.PubSub.Udp.Tests.csproj --configuration Release --framework net10.0 -p:CustomTestTarget=net10.0 — 227 passed.
  • dotnet test tests\Opc.Ua.PubSub.Tests\Opc.Ua.PubSub.Tests.csproj --configuration Release --framework net48 -p:CustomTestTarget=net48 — 1,324 passed, 1 skipped.
  • dotnet test tests\Opc.Ua.PubSub.Udp.Tests\Opc.Ua.PubSub.Udp.Tests.csproj --configuration Release --framework net48 -p:CustomTestTarget=net48 — 175 passed, 4 skipped.

Direct regression coverage includes forged-record replay poisoning, scoped sequence windows and nonce reuse, concurrent sequence serialization, sequence exhaustion, per-responder unsecured Action isolation, and authenticated-peer pinning.

Related Issues

Checklist

  • I have signed the CLA and read the CONTRIBUTING doc.
  • I have added tests that prove my fix is effective or that my feature works and increased code coverage.
  • I have added all necessary documentation.
  • I have verified that my changes do not introduce new build or analyzer warnings.
  • I ran all tests locally using the UA.slnx solution against at least .NET Framework and .NET 10, and all passed.
  • I fixed all failing and flaky tests in the CI pipelines and all CodeQL warnings.
  • I have addressed all PR feedback received.

marcschier and others added 3 commits July 19, 2026 08:27
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@marcschier
marcschier marked this pull request as ready for review July 19, 2026 10:59
Copilot AI review requested due to automatic review settings July 19, 2026 10:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

This PR hardens PubSub replay/nonce handling, Action request security semantics, and DTLS peer/replay protections to align with OPC 10000-14 and RFC 9147 guidance.

Changes:

  • Scopes UADP replay windows by (PublisherId, WriterGroupId, SecurityTokenId) while keeping nonce uniqueness global per token/key, and avoids replay-state mutation on unsigned frames.
  • Serializes DTLS record sequence/crypto use, adds epoch sequence exhaustion handling, and pins CID-less DTLS associations to the authenticated peer endpoint.
  • Makes “allow unsecured Action” an opt-in per responder instead of a connection-wide switch, with new regression/integration tests.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/Opc.Ua.PubSub.Udp.Tests/Dtls/DtlsRecordProtectionTests.cs Adds parallel AEAD sealing/opening test and sequence exhaustion regression.
tests/Opc.Ua.PubSub.Udp.Tests/Dtls/DtlsHandshakeContextTests.cs Extends handshake tests and in-memory channel to validate authenticated-peer pinning.
tests/Opc.Ua.PubSub.Udp.Tests/Dtls/DtlsDatagramTransportPeerTests.cs New integration test ensuring pinned DTLS peer isn’t redirected by spoofed sources.
tests/Opc.Ua.PubSub.Tests/Security/UadpSecurityWrapperReplayTests.cs New tests for scoped replay state and required-mode behavior.
tests/Opc.Ua.PubSub.Tests/Security/SecurityTokenWindowTests.cs Adds nonce-reuse regression across publisher/writer-group scopes.
tests/Opc.Ua.PubSub.Tests/Connections/PubSubConnectionPrivateMethodTests.cs Updates private-method test to pass publisher/writer-group scoping args.
tests/Opc.Ua.PubSub.Tests/Connections/PubSubConnectionCoverageTests.cs Adds coverage for per-responder unsecured Action opt-in isolation.
src/Opc.Ua.PubSub/Security/UadpSecurityWrapper.cs Adds scoped unwrap overload, early required-mode rejection, and scoped replay acceptance paths.
src/Opc.Ua.PubSub/Security/SecurityTokenWindow.cs Implements scoped sequence windows and global nonce tracking; adds scoped interface implementation.
src/Opc.Ua.PubSub/Security/ISecurityTokenWindow.cs Clarifies legacy contract and introduces internal scoped window interface.
src/Opc.Ua.PubSub/Connections/PubSubConnection.cs Moves unsecured Action policy to per-responder; passes publisher/writer-group to unwrap.
src/Opc.Ua.PubSub.Udp/UdpDatagramTransport.cs Adds ability to pin authenticated remote endpoint; makes “last seen peer” tracking configurable.
src/Opc.Ua.PubSub.Udp/Dtls/IDtlsDatagramChannel.cs Adds optional authenticated-peer pinning channel capability.
src/Opc.Ua.PubSub.Udp/Dtls/DtlsRecordProtection.cs Serializes sealing/opening/dispose, reconstructs sequence safely, rejects sequence exhaustion.
src/Opc.Ua.PubSub.Udp/Dtls/DtlsHandshakeContext.cs Filters handshake frames by expected source and pins authenticated peer on completion.
src/Opc.Ua.PubSub.Udp/Dtls/DtlsDatagramTransport.cs Pins CID-less DTLS association to authenticated peer; filters spoofed sources before replay/crypto.

Comment thread src/Opc.Ua.PubSub/Security/UadpSecurityWrapper.cs
Comment thread src/Opc.Ua.PubSub/Security/UadpSecurityWrapper.cs
Comment thread src/Opc.Ua.PubSub/Security/SecurityTokenWindow.cs Outdated
Comment thread tests/Opc.Ua.PubSub.Udp.Tests/Dtls/DtlsRecordProtectionTests.cs Outdated
Comment thread tests/Opc.Ua.PubSub.Udp.Tests/Dtls/DtlsDatagramTransportPeerTests.cs Outdated
@marcschier
marcschier marked this pull request as draft July 19, 2026 11:35
@marcschier
marcschier marked this pull request as ready for review July 19, 2026 11:55
Defer replay-state commits until authentication completes, bound nonce tracking with a Bloom filter, and harden DTLS tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@marcschier
marcschier marked this pull request as draft July 19, 2026 16:43
@marcschier
marcschier marked this pull request as ready for review July 19, 2026 16:55
@marcschier
marcschier requested a review from romanett July 19, 2026 18:37
@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.81073% with 64 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.62%. Comparing base (70c74e7) to head (0b00dba).
⚠️ Report is 14 commits behind head on master.

Files with missing lines Patch % Lines
src/Opc.Ua.PubSub.Udp/Dtls/DtlsHandshakeContext.cs 0.00% 59 Missing ⚠️
...rc/Opc.Ua.PubSub.Udp/Dtls/DtlsDatagramTransport.cs 90.90% 1 Missing and 2 partials ⚠️
src/Opc.Ua.PubSub/Security/SecurityTokenWindow.cs 98.71% 0 Missing and 1 partial ⚠️
src/Opc.Ua.PubSub/Security/UadpSecurityWrapper.cs 98.46% 0 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #4044      +/-   ##
==========================================
+ Coverage   73.55%   73.62%   +0.06%     
==========================================
  Files        1345     1345              
  Lines      180036   180294     +258     
  Branches    31677    31707      +30     
==========================================
+ Hits       132427   132739     +312     
+ Misses      36911    36840      -71     
- Partials    10698    10715      +17     
Files with missing lines Coverage Δ
src/Opc.Ua.PubSub.Udp/Dtls/DtlsRecordProtection.cs 87.01% <100.00%> (+1.73%) ⬆️
src/Opc.Ua.PubSub.Udp/Dtls/IDtlsDatagramChannel.cs 0.00% <ø> (ø)
src/Opc.Ua.PubSub.Udp/UdpDatagramTransport.cs 69.96% <100.00%> (+0.85%) ⬆️
src/Opc.Ua.PubSub/Connections/PubSubConnection.cs 88.39% <100.00%> (+1.11%) ⬆️
src/Opc.Ua.PubSub/Security/SecurityTokenWindow.cs 98.61% <98.71%> (+2.91%) ⬆️
src/Opc.Ua.PubSub/Security/UadpSecurityWrapper.cs 95.77% <98.46%> (+0.68%) ⬆️
...rc/Opc.Ua.PubSub.Udp/Dtls/DtlsDatagramTransport.cs 66.66% <90.90%> (+66.66%) ⬆️
src/Opc.Ua.PubSub.Udp/Dtls/DtlsHandshakeContext.cs 0.00% <0.00%> (ø)

... and 33 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Raise changed-line patch coverage of the replay/DTLS hardening changes to
~100% by covering the newly introduced branches with deterministic NUnit
tests (no reflection, minimal mocking):

- SecurityTokenWindow: scoped replay-state cleanup on token retirement
  while other tokens' scopes are retained.
- UadpSecurityWrapper: legacy (non-scoped) token-window fallback and
  unsigned scoped frames that must not consume authenticated replay state.
- PubSubConnection: per-responder unsecured JSON Action rejection and
  opportunistic unwrap of a secured frame when security is not required.
- DtlsDatagramTransport: authenticated-peer null and conflicting-rebind
  guards, plus close clearing the pinned peer.
- DtlsRecordProtection: initial record-sequence validation and idempotent
  dispose.
- UdpDatagramTransport: connected send client rejects a null pin and
  ignores redirect attempts to a different endpoint.

New tests pass on net10.0 (Release) and, where DTLS is supported, on net48.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@marcschier marcschier added the ready Ready to merge once CI Passes label Jul 21, 2026
@marcschier
marcschier merged commit 9ada62b into OPCFoundation:master Jul 21, 2026
164 checks passed
@marcschier
marcschier deleted the marcschier/review-fix/pubsub-dtls-security branch July 25, 2026 10:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready Ready to merge once CI Passes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants